package com.virtual.repository.plugins.neo4j;

import com.virtual.repository.common.queries.Query;
import com.virtual.repository.common.queries.ResultEntity;
import com.virtual.repository.plugins.api.IDatabasePlugin;
import org.neo4j.rest.graphdb.RestAPI;
import org.neo4j.rest.graphdb.RestAPIFacade;
import org.neo4j.rest.graphdb.query.RestCypherQueryEngine;
import org.neo4j.rest.graphdb.util.QueryResult;

import java.net.UnknownHostException;
import java.util.*;

/**
 * Autor: Damian Pietrzykowski.
 */
public class Neo4jDBDriver implements IDatabasePlugin {

    private String serverPort;

    private String serverAddress;

    private String dbPath;

    private String username;

    private String password;

    private RestAPI graphDb;

    private RestCypherQueryEngine engine;

    public Neo4jDBDriver(){};

    public Neo4jDBDriver(String serverAddress, String serverPort, String username, String password, String dbPath){
        this.serverAddress = serverAddress;
        this.serverPort = serverPort;
        this.username = username;
        this.password = password;
        this.dbPath = dbPath;
    };

    @Override
    public void connect() throws UnknownHostException {
        System.out.println("Connect to neo4j server: http://"+this.serverAddress+":"+this.serverPort+""+this.dbPath);
        graphDb = new RestAPIFacade("http://"+this.serverAddress+":"+this.serverPort+""+this.dbPath);
        engine = new RestCypherQueryEngine(graphDb);
    }

    @Override
    public void disconnect() {
        graphDb.close();
    }

    @Override
    public Collection<ResultEntity> executeQuery(Query query) {
        QueryResult<Map<String,Object>> result  =  engine.query(buildCypherQuery(query), Collections.EMPTY_MAP);
        ArrayList<ResultEntity> entities = new ArrayList<>();
        for (Map<String, Object> row : result){
            entities.add(parseRow(row));
        }
        return entities;
    }

    @Override
    public ResultEntity executeQueryForSingleResult(Query query) {
        ResultEntity resultEntity = null;
        QueryResult<Map<String,Object>> result  =  engine.query(buildCypherQuery(query), Collections.EMPTY_MAP);
        if(result.iterator().hasNext()) {
           Map<String, Object> row = result.iterator().next();
           resultEntity = parseRow(row);
       }
        return resultEntity;
    }

    @Override
    public long executeQueryForCount(Query query) {
        //TODO - liczba zwracana przez query
        QueryResult<Map<String,Object>> result  =  engine.query(buildCypherQuery(query), Collections.EMPTY_MAP);
        int count=0;
        for (Map<String, Object> row : result){
            count+=row.size();
        }
        return count;
    }

    public ResultEntity parseRow(Map<String,Object> result){
        Iterator itKey = result.keySet().iterator();
        ResultEntity resultEntity = new ResultEntity();
        while (itKey.hasNext()) {
            String key = "" + itKey.next();
            resultEntity.setField(key.substring(2), result.get(key));
        }
        return resultEntity;
    }

	private String buildCypherQuery(Query query) {
		StringBuilder cypher = new StringBuilder();
		cypher.append("match (n:" + query.getEntityName() + ") ");

		if (query.getSelectionCriteria() != null && !query.getSelectionCriteria().isEmpty()) {
			cypher.append(" WHERE ");

			for (int i = 0; i < query.getSelectionCriteria().size(); i++) {
				cypher.append("n." + query.getSelectionCriteria().get(i));
			}
		}

		cypher.append(" return ");
		boolean isFirst = true;
		for (String field : query.getSelectedFields()) {
			if (!isFirst) {
				cypher.append(",n." + field);
				isFirst = false;
			} else {
				cypher.append("n." + field);
			}
		}

		cypher.append(";");
		return cypher.toString();
	}

    public String getServerPort() {
        return serverPort;
    }

    public void setServerPort(String serverPort) {
        this.serverPort = serverPort;
    }

    public String getServerAddress() {
        return serverAddress;
    }

    public void setServerAddress(String serverAddress) {
        this.serverAddress = serverAddress;
    }

    public String getDbPath() {
        return dbPath;
    }

    public void setDbPath(String dbPath) {
        this.dbPath = dbPath;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}
